<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <style>
        body {
            height: 100vh;
            margin: 0;
        }

        body,
        html,
        #glcanvas {
            overflow: hidden
        }

        canvas {
            display: block;
            width: 100vw;
            height: 100vh;
        }
    </style>
</head>

<body>
    <canvas id="glcanvas"></canvas>
    <script id="vertex-shader" type="x-shader/x-vertex">
    attribute vec2 a_position;
    void main() {
      gl_Position = vec4(a_position, 0.0, 1.0);
    }
  </script>
    <script id="fragment-shader" type="x-shader/x-fragment">
#ifdef GL_ES
precision mediump float;
#endif

uniform float u_time;
uniform vec2 u_resolution;

vec3 palette( float t ) {
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.25,0.4,0.0);

    return a + b*cos( 6.28318*(c*t+d) );
}
float oscillate(float time, float minVal, float maxVal) {
    float sineWave = sin(time);
    float normalizedSine = (sineWave + 1.0) / 2.0;
    return mix(minVal, maxVal, normalizedSine);
}

vec3 getRayDirection(vec2 uv, vec3 camPos, vec3 lookAt, float fov) {
    vec3 forward = normalize(lookAt - camPos);
    vec3 right = normalize(cross(vec3(0.0, 1.0, 0.0), forward));
    vec3 up = cross(forward, right);
    
    vec3 dir = forward + uv.x * right * fov + uv.y * up * fov;
    return normalize(dir);
}
float hash(vec3 p) {
    p = 50.0 * fract(p * 0.3183099 + vec3(0.71, 0.113, 0.5));
    return -1.0 + 2.0 * fract(p.x * p.y * p.z * (p.x + p.y + p.z));
}

float noise(vec3 p) {
    vec3 i = floor(p);
    vec3 f = fract(p);
    float a = hash(i);
    float b = hash(i + vec3(1.0, 0.0, 0.0));
    float c = hash(i + vec3(0.0, 1.0, 0.0));
    float d = hash(i + vec3(1.0, 1.0, 0.0));
    float e = hash(i + vec3(0.0, 0.0, 1.0));
    float f1 = hash(i + vec3(1.0, 0.0, 1.0));
    float g = hash(i + vec3(0.0, 1.0, 1.0));
    float h = hash(i + vec3(1.0, 1.0, 1.0));
    vec3 u = f * f * (3.0 - 2.0 * f);
    return mix(mix(mix(a, b, u.x), mix(c, d, u.x), u.y), mix(mix(e, f1, u.x), mix(g, h, u.x), u.y), u.z);
}

float sdfSphereGrid(vec3 p, float sphereRadius, float spacing) {
    float noisySphere = noise(p/2.0 + u_time/4.0);
    p = mod(p + noisySphere, spacing) - spacing * 0.5;
    float dist = (length(p)) - sphereRadius;
    return dist;
}

float rayMarch(vec3 ro, vec3 rd, float cameraRadius) {
    float t = 0.0;
    float maxDist = 200.0;
    for (int i = 0; i < 200; i++) {
        vec3 p = ro + rd * t;
        float dist = sdfSphereGrid(p, 0.9, 2.9);
        if (length(p - ro) < cameraRadius) {
            t += 0.01;
            continue;
        }
        
        if (dist < 0.0) {
            return t;
        }
        t += max(dist * 0.5, 0.01);
    }
    return maxDist;
}

vec3 calculateCameraPosition(float time) {
    float radius = 10.0;
    float speed = 0.5;

    float xPos = radius * sin(time * speed);
    float yPos = radius * cos(time * speed);
    float zPos = time * 0.5;

    return vec3(xPos, yPos, zPos);
}

vec3 calculateLookAtDirection(float time) {
    float speed = 0.5;
    float xDir = cos(time * speed);
    float yDir = -sin(time * speed);
    float zDir = 1.0;

    return normalize(vec3(xDir, yDir, zDir));
}

void main() {
    vec2 uv = (gl_FragCoord.xy / u_resolution.xy) * 2.0 - 1.0;
    uv.x *= u_resolution.x / u_resolution.y;

    vec3 camPos = calculateCameraPosition(u_time/4.0);
    vec3 lookAt = camPos + calculateLookAtDirection(u_time/4.0);

    vec3 rayDir = getRayDirection(uv, camPos, lookAt, 1.5);
    float cameraRadius = 1.5;

    float dist = rayMarch(camPos, rayDir, cameraRadius);
    vec3 p = camPos + rayDir * dist;
    float cameraToPointDist = length(camPos - p);

    vec3 color = vec3(0.0);
    if (dist < 200.0) {
     if(dist < 100.0){
   color = palette(cameraToPointDist * exp(-length(p)/600.0) * 0.05 + u_time/10.0);         }
    } else {
        color = vec3(0.0);
    }

    gl_FragColor = vec4(color, 1.0);
}
</script>
</body>
<script>
    const canvas = document.getElementById('glcanvas');
    const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');

    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;

    function getShaderSource(id) {
        return document.getElementById(id).textContent;
    }

    const vertexShaderSource = getShaderSource('vertex-shader');
    const fragmentShaderSource = getShaderSource('fragment-shader');

    function createShader(gl, type, source) {
        const shader = gl.createShader(type);
        gl.shaderSource(shader, source);
        gl.compileShader(shader);
        if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
            console.error('An error occurred compiling the shaders:', gl.getShaderInfoLog(shader));
            gl.deleteShader(shader);
            return null;
        }
        return shader;
    }

    function createProgram(gl, vertexShader, fragmentShader) {
        const program = gl.createProgram();
        gl.attachShader(program, vertexShader);
        gl.attachShader(program, fragmentShader);
        gl.linkProgram(program);
        if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
            console.error('Unable to initialize the shader program:', gl.getProgramInfoLog(program));
            return null;
        }
        return program;
    }

    const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
    const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
    const program = createProgram(gl, vertexShader, fragmentShader);

    const positionBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
    const positions = [
        -1.0, -1.0,
        1.0, -1.0,
        -1.0, 1.0,
        1.0, 1.0,
    ];
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

    const positionLocation = gl.getAttribLocation(program, 'a_position');
    gl.enableVertexAttribArray(positionLocation);
    gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

    gl.useProgram(program);

    const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
    const timeLocation = gl.getUniformLocation(program, 'u_time');

    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    let startTime = null;

    function render(time) {
        if (!startTime) startTime = time; // Set the start time only once.

        const elapsedTime = (time - startTime) * 0.001; // Convert time to seconds.

        gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
        gl.uniform1f(timeLocation, elapsedTime); // Pass elapsed time to the shader.
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        requestAnimationFrame(render);
    }

    resize();
    function resize() {
        const devicePixelRatio = Math.min(window.devicePixelRatio || 1, 2);
        canvas.width = Math.floor(window.innerWidth * devicePixelRatio);
        canvas.height = Math.floor(window.innerHeight * devicePixelRatio);
        gl.viewport(0, 0, canvas.width, canvas.height);
        canvas.style.width = window.innerWidth + "px";
        canvas.style.height = window.innerHeight + "px";
        document.documentElement.requestFullscreen();
    }

    window.addEventListener('resize', resize);

    requestAnimationFrame(render);

</script>

</html>